﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using ColladaDotNet.Geometry;
using Microsoft.DirectX;

namespace BakalarskaPraca.Generation.Data
{
    public class GenerationTarget : HumidityPolygon
    {
        private readonly Bitmap _humidityMap;
        private readonly Bitmap _shadowLayer; 
        private readonly ImageAttributes _shadowBlendingAttributes;

        public GenerationTarget(HumidityPolygon polygon, int iterationCount)
        {
            this.CopyFrom(polygon);
            this._humidityMap = new Bitmap(this.Width, this.Height);
            this._shadowLayer = new Bitmap(this.Width, this.Height);
            Graphics.FromImage(this._humidityMap).Clear(Color.White);
            Graphics.FromImage(this._shadowLayer).Clear(Color.Transparent);

            float[][] matrixItems = { 
                new float[] {1, 0, 0, 0, 0},
                new float[] {0, 1, 0, 0, 0},
                new float[] {0, 0, 1, 0, 0},
                new float[] {0, 0, 0, 1f / iterationCount, 0}, 
                new float[] {0, 0, 0, 0, 1}
            };
            this._shadowBlendingAttributes = new ImageAttributes();
            this._shadowBlendingAttributes.SetColorMatrix(
                new ColorMatrix(matrixItems),
                ColorMatrixFlag.Default,
                ColorAdjustType.Bitmap);
        
        }

        public GenerationTarget(Polygon polygon, int iterationCount) : base(polygon)
        {
            if (polygon.Vertices.Count < 3)
            {
                throw new ArgumentException("Bad generation target polygon input");
            }

            this._humidityMap = new Bitmap(this.Width, this.Height);
            this._shadowLayer = new Bitmap(this.Width, this.Height);
            Graphics.FromImage(this._humidityMap).Clear(Color.White);
            Graphics.FromImage(this._shadowLayer).Clear(Color.Transparent);

            float[][] matrixItems = { 
                new float[] {1, 0, 0, 0, 0},
                new float[] {0, 1, 0, 0, 0},
                new float[] {0, 0, 1, 0, 0},
                new float[] {0, 0, 0, 1f / iterationCount, 0}, 
                new float[] {0, 0, 0, 0, 1}
            };
            this._shadowBlendingAttributes = new ImageAttributes();
            this._shadowBlendingAttributes.SetColorMatrix(
                new ColorMatrix(matrixItems),
                ColorMatrixFlag.Default,
                ColorAdjustType.Bitmap);
        }

       

        public void DropShadow(Polygon shade, Vector3 direction)
        {
            List<Vector3> cutted = this.CutInvisibleChunks(shade, direction);

            if (cutted.Count <= 2)
                return;

            Vector3[] vectors = cutted.Select(v =>
                                                  {
                                                      Vector3 u =
                                                          new Vector3(
                                                              v.X + direction.X,
                                                              v.Y + direction.Y,
                                                              v.Z + direction.Z);
                                                      return
                                                          Plane.IntersectLine(
                                                              this.Plane, v, u);
                                                  }).ToArray();
            
            for (int i = 0; i < vectors.Count(); i++)
            {
                vectors[i].TransformCoordinate(this.TransformMatrix);
            }

            PointF[] points = vectors.Select(v =>
            {
                float x = (v.X + this.OffsetX) * ScaleFactor;
                float y = (v.Y + this.OffsetY) * ScaleFactor;

                return new PointF(x, y);
            }).ToArray();
            Graphics g = Graphics.FromImage(this._shadowLayer);
            g.FillPolygon(Brushes.Black, points);
        }

        public Bitmap GetPreview()
        {
            return this._humidityMap;
        }

        public void ApplyShadowLayer()
        {
            Graphics g = Graphics.FromImage(this._humidityMap);
            int w = this._shadowLayer.Width;
            int h = this._shadowLayer.Height;

            g.DrawImage(
                    this._shadowLayer,
                    new Rectangle(0, 0, w, h),
                    0,
                    0,
                    w,
                    h,
                    GraphicsUnit.Pixel,
                    this._shadowBlendingAttributes
                );
            Graphics.FromImage(this._shadowLayer).Clear(Color.Transparent);
        }

        public unsafe byte[][] GetHumidity()
        {
            int width = this._humidityMap.Width;
            int height = this._humidityMap.Height;

            byte[][] result = new byte[width][];
            for(int i = 0; i < width; i++)
            {
                result[i] = new byte[height];
            }

            BitmapData map = this._humidityMap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly,
                                       this._humidityMap.PixelFormat);
            int mapPixelSize  = 4;
            
            IntPtr mapHandle = map.Scan0;
            
            for(int y = 0; y < height; y++)
            {
                byte* mapRow  = (byte*)mapHandle  + (y * map.Stride);
                for (int x = 0; x < width; x++)
                {
                    if (this.HumidityMaskValues[x][y])
                    {
                        result[x][y] = (byte)(255 - mapRow[x * mapPixelSize]);
                    }
                    else
                    {
                        result[x][y] = 0;
                    }
                }
            }

            this._humidityMap.UnlockBits(map);
            this.HumidityMapValues = result;
            
            return result;
        }

        private static int ii;
        public unsafe Bitmap PreviewHumidity(byte[][] humidity)
        {
            int width = humidity.Count();
            int height = humidity[0].Count();

            Bitmap result = new Bitmap(width, height);
            
            BitmapData data = result.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly,
                                       this._humidityMap.PixelFormat);
            
            int pixelSize = 4;

            IntPtr dataHandle = data.Scan0;
            
            for (int y = 0; y < height; y++)
            {
                byte* dataRow = (byte*)dataHandle + (y * data.Stride);
                for (int x = 0; x < width; x++)
                {
                    dataRow[x * pixelSize]     = (byte)(this.HumidityMaskValues[x][y] ? humidity[x][y] : 0);
                    dataRow[x * pixelSize + 1] = (byte)(this.HumidityMaskValues[x][y] ? 0 : 255);
                    dataRow[x * pixelSize + 2] = (byte)(this.HumidityMaskValues[x][y] ? 255 - humidity[x][y] : 0);
                    dataRow[x * pixelSize + 3] = 255;
                }
            }
            
            result.UnlockBits(data);

            return result;
        }

        public List<Vector3> CutInvisibleChunks(Polygon source, Vector3 direction)
        {
            Vector3 normalizedDirection = new Vector3(direction.X, direction.Y, direction.Z);
            normalizedDirection.Normalize();
            List<Vector3> result = new List<Vector3>();
            for (int i = 0; i < source.Vertices.Count; i++)
            {
                Vector3 p1 = source.Vertices[i].Positions[0].Vector;
                Vector3 p2 = i == source.Vertices.Count - 1 ? source.Vertices[0].Positions[0].Vector : source.Vertices[i + 1].Positions[0].Vector;
                
                Vector3 intersectionPoint = Plane.IntersectLine(this.Plane, p1, p2);
                result.Add(p1);
               
                if (intersectionPoint.X >= Math.Min(p1.X, p2.X) && intersectionPoint.X <= Math.Max(p1.X, p2.X) &&
                    intersectionPoint.Y >= Math.Min(p1.Y, p2.Y) && intersectionPoint.Y <= Math.Max(p1.Y, p2.Y) &&
                    intersectionPoint.Z >= Math.Min(p1.Z, p2.Z) && intersectionPoint.Z <= Math.Max(p1.Z, p2.Z))
                {
                    result.Add(intersectionPoint);
                }
            }
            return result.Where(vector =>
                                    {
                                        bool res = this.IsVisible(vector, direction);
                                        return res;
                                    }).ToList();
        }

        private bool IsVisible(Vector3 target, Vector3 direction)
        {
            target.TransformCoordinate(this.TransformMatrix);
            direction.TransformCoordinate(this.TransformMatrix);
            Plane p = this.Plane;
            p.Transform(this.TransformMatrix);

            if (Math.Abs(target.Z + p.D / p.C) <= Properties.Settings.Default.Tolerance)
                return true;
            return Math.Sign(direction.Z) != Math.Sign(target.Z + p.D / p.C);
        }

        public Vector3 RealPosition(Vector3 position)
        {
            Vector3 result = new Vector3(position.X / ScaleFactor - this.OffsetX,
                                        position.Y / ScaleFactor - this.OffsetY, 
                                        position.Z);
            result.TransformCoordinate(this.InversedTransformMatrix);
            result.Z = position.Z + this.GetZPoisiton(result.X, result.Y);
            return result;
        }

        public bool Equals(Polygon polygon)
        {
            return polygon.Vertices == this._vertices;
        }
    }
}
